#include "block_table.h"
#include "block_table_register.h"
#include "block.h"

BlockTable::BlockTable(const std::string& path,unsigned int blockSize){
	std::string free_name = path + ".free_blocks";	
	free_space_stream.open(free_name.c_str(), std::fstream::binary | std::fstream::in | std::fstream::out);
	if(!free_space_stream){
		free_space_stream.open(free_name.c_str(), std::fstream::binary | std::fstream::in | std::fstream::out | std::fstream::trunc);
	}
	this->blockSize = blockSize;
}
		
BlockTable::~BlockTable(){
}
		
std::pair<BlockTableRegister,bool> BlockTable::getAvailBlock(const Register& reg){
	std::pair<BlockTableRegister,bool> p;
	p.first.free_space = blockSize;
	p.first.block_number = 0;

	// Busco un bloque con espacio...
	do{
		p.first.block_number++;
		p.first = readBlockTableRegister(p.first);

		/* El registro no solo gasta lugar el mismo sino que tambien
           lo hace su info de control */
		unsigned int regSize = Block::getDataControlSize()+reg.getSize();
		//hay espacio en el bloque
		p.second = ( p.first.free_space >= regSize );
	}while(p.first.block_number && !p.second);
			
	/* no se encontro ninguno disponible se crea uno nuevo */
	if(!p.second){
		BlockPointer bp(0,0);
		findLast(bp);
		p.first.block_number = bp.block + 1;
		p.first.free_space = blockSize;
	}
			
	return	p;
}

void BlockTable::writeBlockTableRegister(const BlockTableRegister& btr){
	free_space_stream.clear();
	free_space_stream.seekp((btr.block_number-1) * (BlockTableRegister::getSize()), std::fstream::beg);
	
	char* bytes = new char[BlockTableRegister::getSize()];
	btr.toBytes(bytes);
	free_space_stream.write( bytes, BlockTableRegister::getSize());
	delete[] bytes;
}

BlockTableRegister BlockTable::readBlockTableRegister(const BlockTableRegister& btr){
	BlockTableRegister ret(0,0);	
	if(btr.block_number<=0)
		return ret;

	free_space_stream.clear();
	free_space_stream.seekg((btr.block_number-1)*BlockTableRegister::getSize(), std::fstream::beg);	
	free_space_stream.peek();
	if(!free_space_stream.eof()){
		char* bytes = new char[BlockTableRegister::getSize()];	
		free_space_stream.read( bytes, BlockTableRegister::getSize());
		ret = BlockTableRegister::toTableRegister(bytes);		
		delete[] bytes;
	}
	return ret;
}

bool BlockTable::find(const BlockPointer& bp){
	BlockTableRegister btr(0,bp.block);
	btr = readBlockTableRegister(btr);
	return (btr.block_number);
}

void BlockTable::findLast(BlockPointer& bp){
	free_space_stream.clear();
	free_space_stream.seekg(0, std::fstream::end);		
	unsigned int size = free_space_stream.tellg();
	if(!size)return;	

	unsigned int nRegs = size/BlockTableRegister::getSize();
	free_space_stream.seekg((nRegs-1)*BlockTableRegister::getSize(), std::fstream::beg);		
	
	char* bytes = new char[BlockTableRegister::getSize()];
	free_space_stream.read( bytes, BlockTableRegister::getSize());
	BlockTableRegister btr = BlockTableRegister::toTableRegister(bytes);
	delete[] bytes;
	bp.block = btr.block_number;
}

void BlockTable::createChain(std::list<unsigned long int>& chain,unsigned int nBlocks ){
	/* Busco los primero nBlocks libres contiguos */
	bool next=true;
	BlockTableRegister btr(0,1);
	btr = readBlockTableRegister(btr);
	while(btr.block_number && (chain.size()<nBlocks)){
		while( (chain.size()<nBlocks) && (btr.block_number) && next){
			if(btr.free_space >= blockSize-Block::getHeaderSize() ){
				chain.push_back(btr.block_number);
				
				btr.block_number++;
				btr = readBlockTableRegister(btr);
			}else{
				/* No se llego a NBlocks contiguos se debe comenzar nuevamente */
				next = false;
				chain.clear();
			}
		}
		next = true;
		btr.block_number++;
		btr = readBlockTableRegister(btr);
	}
		
	unsigned int start;
	if( chain.size() < nBlocks && !chain.size()){
		/* Hay que crear toda la cadena nueva */		
		start = 0;
	}else{
		/* Hay que concatenar algunos bloques nuevos al final  */
		start = chain.size();
	}
	
	/* Concateno algunos bloques nuevos al final en el caso que haga falta */
	BlockPointer bp(0,0);
	findLast(bp);
	bp.block++;
	for(unsigned int c=start;c<nBlocks;c++){
		chain.push_back(bp.block);
		bp.block++;
	}
}
